package ua.edu.nuos.jeetraining2012.cms.model;

import org.slf4j.Logger;
import ua.edu.nuos.jeetraining2012.cms.entity.StudentEntity;
import ua.edu.nuos.jeetraining2012.cms.entity.UserEntity;
import ua.edu.nuos.jeetraining2012.cms.model.exception.DAOException;
import ua.edu.nuos.jeetraining2012.cms.service.exception.ServiceException;

import javax.ejb.Local;
import javax.ejb.Stateless;
import javax.persistence.*;
import java.util.Arrays;

/**
 * StudentDAO class.
 * <p/>
 * DAO class that works with students table.
 * Available actions are:
 * <ul>
 * <li>Add student: {@link StudentDAO#create(ua.edu.nuos.jeetraining2012.cms.entity.StudentEntity)}</li>
 * <li>Find student: {@link StudentDAO#find(ua.edu.nuos.jeetraining2012.cms.entity.StudentEntity)}</li>
 * <li>Update student: {@link StudentDAO#update(ua.edu.nuos.jeetraining2012.cms.entity.StudentEntity)}</li>
 * <li>Delete student: {@link StudentDAO#delete(ua.edu.nuos.jeetraining2012.cms.entity.StudentEntity)}</li>
 * </ul>
 * All methods accepted StudentEntityObject.
 *
 * @author Victor N. Ganichev victor.ganichev@gmail.com
 * @version 1.0 Date: 02.05.13 Time: 16:20
 * @since 1.0
 */
@Local
@Stateless
public class StudentDAO {

    /**
     * Define logger
     */
    final private Logger logger = org.slf4j.LoggerFactory.getLogger(this.getClass());

    /**
     * Define EntityManager
     * All transactions will be controlled by Application server.
     */
    @PersistenceContext
    private EntityManager em;

    // Getters & Setters
    protected EntityManager getEm() {
        return em;
    }

    private void setEm(EntityManager em) {
        this.em = em;
    }

// Constructors

    /**
     * Default constructor.
     */
    public StudentDAO() {
        logger.debug("StudentDAO start.");
    }

    /**
     * Constructor with customized EntityManager.
     * Use this constructor for testing purposes.
     *
     * @param em Instance of EntityManager.
     */
    public StudentDAO(EntityManager em) {
        logger.debug("StudentDAO start with EntityManager {}", em);
        setEm(em);
    }

// Actions

    /**
     * Create.
     * <p/>
     * Add new student to DB. In case of success, method will return an ID of just inserted student.
     * In other cases, when create command can't complete with success, {@link DAOException} will be
     * thrown and some useful information will be send into logger.
     *
     * @param dto Instance of StudentEntity.
     * @return Long id of just inserted student.
     * @throws DAOException             In cases, when can't persists into db or some parent Exceptions
     *                                  were thrown.
     * @throws IllegalArgumentException In case, when Entity is null.
     */
    public Long create(StudentEntity dto) throws DAOException, IllegalArgumentException {
        logger.debug("create() - start.");

        if (dto == null) {
            logger.warn("Null StudentEntity.");
            logger.debug("create() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("Can't persists with null valued Entity.");
        }

        Long studentLastInsertId = null;
        logger.debug("Adding student with values: {}", dto);
        try {
            em.persist(dto);
            em.flush();
            studentLastInsertId = dto.getId();
            logger.debug("Student successful added. Student ID: {}", studentLastInsertId);
        } catch (Exception e) {
            logger.warn("Can't add student. Error message: " + e.getMessage() + "\nTrace:\n" +
                    Arrays.deepToString(e.getStackTrace()));
            logger.debug("create() - finished with DAOException.");
            throw new DAOException("Unable to add student. Error message: " + e.getMessage());
        }

        logger.debug("create() - finish");
        return studentLastInsertId;
    }

    /**
     * Find.
     * <p/>
     * Find student according to its primary key, that must send in dto.
     * In case of success, retrieved information will be returned as StudentEntity. If student
     * can't be found, method will returns null.
     * In case when for some reason find() method can't be called or it generated some exception,
     * {@link DAOException} will be thrown and new log record will be produced.
     *
     * @param dto Instance of StudentEntity.
     * @return Instance of StudentEntity with retrieved info or null.
     * @throws DAOException             In case, when can't start find() in db or some parent exceptions
     *                                  were thrown.
     * @throws IllegalArgumentException In case, when Entity is null.
     */
    public StudentEntity find(StudentEntity dto) throws DAOException, IllegalArgumentException {
        logger.debug("find() - start.");

        if (dto == null) {
            logger.warn("Null StudentEntity");
            logger.debug("find() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("Can't find with null valued Entity.");
        }
        StudentEntity student = null;

        logger.debug("Trying to find student with criteria: {}", dto);
        try {
            student = em.find(StudentEntity.class, dto.getId());
            logger.debug("Student was successful retrieved: {}", student);
        } catch (Exception e) {
            logger.warn("Error! Can't retrieve student. Message: " + e.getMessage() + "\nTrace:\n" +
                    Arrays.deepToString(e.getStackTrace()));
            logger.debug("find() - finished with DAOException.");
            throw new DAOException("Unable to retrieve student. Error message: " + e.getMessage());
        }

        logger.debug("find() - finish.");
        return student;
    }

    /**
     * Update.
     * <p/>
     * Update student record in DB. To update record, send changed instance of StudentEntity.
     * In case of success, <strong>true</strong> will returned, either method will return <strong>false</strong>.
     * In cases, when exceptions were produced, method will thrown {@link DAOException} and add warning
     * record into logger.
     *
     * @param dto Instance of StudentEntity.
     * @return Return true in case of success, either -- false.
     * @throws DAOException             In case, when can't update() record in db or parent exceptions
     *                                  were thrown.
     * @throws IllegalArgumentException In case, when Entity is null.
     */
    public boolean update(StudentEntity dto) throws DAOException, IllegalArgumentException {
        logger.debug("update() - start.");

        if (dto == null) {
            logger.warn("Null StudentEntity");
            logger.debug("update() - finished with IllegalArgumentException");
            throw new IllegalArgumentException("Can't update null valued Entity.");
        }

        boolean isUpdated = false;


        try {
            Query q = em.createQuery("UPDATE StudentEntity s " +
                    "SET s.phone = :phone, " +
                    "s.skype = :skype, " +
                    "s.education = :education, " +
                    "s.occupation = :occupation, " +
                    "s.photo = :photo, " +
                    "s.birthday = :birthday " +
                    "WHERE s.userId.id = :userId");

            q.setParameter("phone", dto.getPhone());
            q.setParameter("skype", dto.getSkype());
            q.setParameter("education", dto.getEducation());
            q.setParameter("occupation", dto.getOccupation());
            q.setParameter("photo", dto.getPhoto());
            q.setParameter("birthday", dto.getBirthday());
            q.setParameter("userId", dto.getUserId().getId());
            em.flush();

            int updated = q.executeUpdate();
            isUpdated = true;
            logger.debug("Updating student was updated", updated);
        } catch (IllegalStateException e) {
            logger.debug("Updating student IllegalStateException" + e.getMessage());
        } catch (Exception e) {
            logger.debug("Updating student Exception"+e.getMessage());
        }

//        try {
//            em.merge(dto);
//            isUpdated = true;
//            logger.debug("Student updated success");
//        } catch (Exception e) {
//            logger.warn("Error! Can't update student. Message: " + e.getMessage() + "\nTrace:\n" +
//                    Arrays.deepToString(e.getStackTrace()));
//            logger.debug("update() - finished with DAOException.");
//            throw new DAOException("Unable to update student. Error message: " + e.getMessage());
//        }

        logger.debug("update() - finish.");
        return isUpdated;
    }

    /**
     * Delete.
     * <p/>
     * Delete student record from db. In case of success <strong>true</strong> will be returned.
     * In other cases -- <strong>false</strong>. If some parent methods produced exceptions,
     * {@link DAOException} will be thrown.
     *
     * @param dto Instance of StudentEntity, that will be deleted.
     * @return Return true in case of success.
     * @throws DAOException             In case, when can't remove() record from db or parent exceptions
     *                                  were thrown.
     * @throws IllegalArgumentException In case, when Entity is null.
     */
    public boolean delete(StudentEntity dto) throws DAOException, IllegalArgumentException {
        logger.debug("delete() - start.");

        if (dto == null) {
            logger.warn("Null StudentEntity.");
            logger.debug("delete() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("Can't delete with null valued Entity.");
        }
        boolean isDeleted = false;

        logger.debug("Deleting student with values: {}", dto);
        try {
            StudentEntity student = em.find(StudentEntity.class, dto.getId());
            em.remove(student);
            isDeleted = true;
            logger.debug("Student was successful deleted.");
        } catch (Exception e) {
            logger.warn("Error! Can't delete student. Message: " + e.getMessage() + "\nTrace:\n" +
                    Arrays.deepToString(e.getStackTrace()));
            logger.debug("delete() - finished with DAOException.");
            throw new DAOException("Unable to delete student. Error message: " + e.getMessage());
        }

        logger.debug("delete() - finish.");
        return isDeleted;
    }

    /**
     * Find by Id.
     * <p/>
     * Find student by its ID. This method familiar to {@link StudentDAO#find(ua.edu.nuos.jeetraining2012.cms.entity.StudentEntity)}
     * except one, this one uses long id as an argument.
     *
     * @param id Student id.
     * @return StudentEntity object.
     * @throws IllegalArgumentException In case, when id <= 0
     * @throws DAOException             In cases, when parent Exception was occurred.
     */
    public StudentEntity findById(long id) throws IllegalArgumentException, DAOException {
        logger.debug("findByUserId() - start.");

        if (id <= 0) {
            logger.warn("Illegal id. " + id);
            logger.debug("findByUserId() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("Student id can't be less or equal zero.");
        }

        StudentEntity dto;

        logger.debug("Finding student with id {}", id);
        try {
            dto = em.find(StudentEntity.class, id);
            logger.debug("Student was successful found. {}", dto);
        } catch (Exception e) {
            logger.warn("Can't find student. Message {} \nTrace\n", e.getMessage(), Arrays.deepToString(e.getStackTrace()));
            throw new DAOException("Unable to find student. Error message: " + e.getMessage());
        }

        logger.debug("findByUserId() - finish.");
        return dto;
    }


    /**
     * Find student by its ID.
     *
     * @param dto StudentEntity with user_id to be searched for
     * @return StudentEntity Object
     * @throws IllegalArgumentException In case, when dto == null
     * @throws DAOException In cases, when parent Exception was occurred.
     */
    public StudentEntity findByUserId(StudentEntity dto) throws IllegalArgumentException, DAOException {
        logger.debug("findByUserId() - invocation");

        if (dto == null) {
            logger.warn("Null StudentEntity");
            logger.debug("find() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("Can't find with null valued Entity.");
        }
        StudentEntity studentEntity;

        logger.debug("Trying to find student with criteria: {}", dto);

        try {
            Query q = em.createQuery("SELECT c FROM StudentEntity c WHERE c.userId.id like :userId");
            q.setParameter("userId", dto.getUserId().getId());
            studentEntity = (StudentEntity) q.getSingleResult();

            logger.debug("createQuery UserEntity - " + studentEntity);
        } catch (NoResultException e) {
            studentEntity = null;
            logger.debug("Student was not found UserEntity - " + studentEntity);
            logger.warn("Method findByUserId() finish with NoResultException");
        } catch (Exception e) {
            logger.warn("Method findByUserId() finish with Exception");
            throw new DAOException("Student was not found try Exception");
        }

        logger.debug("find() - finish.");
        return studentEntity;
    }
}
